# @Version        : 1.0
# @Create Time    : 2024/3/23 22:30
# @File           : config.py
# @IDE            : PyCharm
# @Desc           : 项目全局配置信息
from urllib.parse import quote_plus
import os
from functools import lru_cache
from typing import List, Dict
from pydantic_settings import (
    BaseSettings,
    PydanticBaseSettingsSource,
    SettingsConfigDict,
    YamlConfigSettingsSource,
)
from pydantic import RedisDsn, MongoDsn, Field, field_validator, computed_field
from ipaddress import IPv4Address

# 项目根目录
root_path: str = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
project_name: str = "src"


class DemoSettings(BaseSettings):
    """项目演示环境配置

    Attributes:
        DEMO_ENV: 是否开启演示功能
        DEMO_WHITE_LIST_PATH: 演示功能白名单路径
        DEMO_BLACK_LIST_PATH: 演示功能黑名单路径
    """

    DEMO_ENV: bool = Field(
        default=False, description="是否开启演示功能：取消所有POST,DELETE,PUT操作权限"
    )
    DEMO_WHITE_LIST_PATH: List[str] = Field(
        default=["/auth/login"], description="演示功能白名单路径"
    )
    DEMO_BLACK_LIST_PATH: List[str] = Field(
        default=["/auth/api/login"], description="演示功能黑名单路径，黑名单优先级更高"
    )


class AuthSettings(BaseSettings):
    """项目认证配置

    Attributes:
        OAUTH_ENABLE: 是否开启登录认证
        SECRET_KEY: JWT令牌签名密钥
        ALGORITHM: JWT签名算法
        ACCESS_TOKEN_EXPIRE_MINUTES: access_token过期时间
        REFRESH_TOKEN_EXPIRE_MINUTES: refresh_token过期时间
        ACCESS_TOKEN_CACHE_MINUTES: access_token缓存时间
        ALL_PERMISSION: 所有权限标识
        SUPER_ADMIN: 管理员标识
    """

    OAUTH_ENABLE: bool = Field(default=True, description="是否开启登录认证")
    SECRET_KEY: str = Field(
        default="vgb0tkhjk58+6n-6h-ea&u^1#s0ccp!794=kbvqacjqad75vzps$",
        description="JWT令牌签名密钥",
    )
    ALGORITHM: str = Field(default="HS256", description="JWT签名算法")
    ACCESS_TOKEN_EXPIRE_MINUTES: int = Field(
        default=1440, description="access_token过期时间(分钟)"
    )
    REFRESH_TOKEN_EXPIRE_MINUTES: int = Field(
        default=2880, description="refresh_token过期时间(分钟)"
    )
    ACCESS_TOKEN_CACHE_MINUTES: int = Field(
        default=120, description="access_token缓存时间(分钟)"
    )
    ALL_PERMISSION: str = Field(default="*:*:*", description="所有权限标识")
    SUPER_ADMIN: str = Field(default="ROLE_ADMIN", description="管理员标识")

    @field_validator("SECRET_KEY")
    def validate_secret_key(cls, v: str) -> str:
        """验证密钥长度和复杂度"""
        if len(v) < 32:
            raise ValueError("密钥长度必须大于32位")
        return v


class DataBaseSettings(BaseSettings):
    """数据库基础配置

    Attributes:
        DB_USERNAME: 数据库用户名
        DB_PASSWORD: 数据库密码
        DB_HOST: 数据库主机地址
        DB_PORT: 数据库端口
        DB_NAME: 数据库名称
    """

    DB_USERNAME: str = Field(default="root", description="数据库用户名")
    DB_PASSWORD: str = Field(default="", description="数据库密码")
    DB_SCHEMA: str = Field(default="mysql+pymysql", description="数据库Schema")
    DB_HOST: str = Field(default="127.0.0.1", description="数据库主机地址")
    DB_PORT: int = Field(default=3306, description="数据库端口")
    DB_NAME: str = Field(default="src", description="数据库名称")

    @computed_field
    @property
    def DB_URL(self) -> str:
        """数据库连接URL"""
        return f"{self.DB_SCHEMA}://{quote_plus(self.DB_USERNAME)}:{quote_plus(self.DB_PASSWORD)}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"


class ORMConfig(BaseSettings):
    """数据库ORM配置

    Attributes:
        DATA_SOURCES: 数据库连接配置
        ORM_DB_ECHO: 是否输出SQL语句
    """

    DATA_SOURCES: Dict[str, DataBaseSettings] = Field(..., description="数据库连接配置")
    ORM_DB_ECHO: bool = Field(default=False, description="是否输出SQL语句")


class RedisConfig(BaseSettings):
    """Redis配置

    Attributes:
        REDIS_USERNAME: Redis用户名
        REDIS_PASSWORD: Redis密码
        REDIS_HOST: Redis主机地址
        REDIS_PORT: Redis端口
        REDIS_DB: Redis数据库编号
        REDIS_DB_ENABLE: 是否启用Redis
    """

    REDIS_USERNAME: str = Field(default="root", description="Redis用户名")
    REDIS_PASSWORD: str = Field(default="", description="Redis密码")
    REDIS_HOST: str = Field(default="127.0.0.1", description="Redis主机地址")
    REDIS_PORT: int = Field(default=6379, description="Redis端口")
    REDIS_DB: int = Field(default=0, description="Redis数据库编号")
    REDIS_DB_ENABLE: bool = Field(..., description="是否启用Redis")

    @computed_field
    @property
    def REDIS_DB_URL(self) -> RedisDsn:
        """Redis连接URL"""
        return RedisDsn.build(
            scheme="redis",
            host=self.REDIS_HOST,
            port=self.REDIS_PORT,
            path=f"/{self.REDIS_DB}",
            username=self.REDIS_USERNAME,
            password=self.REDIS_PASSWORD,
        )


class MongoConfig(BaseSettings):
    """MongoDB配置

    Attributes:
        MONGO_DB_ENABLE: 是否启用MongoDB
        MONGO_DB_URL: MongoDB连接URL
        MONGO_DB_NAME: MongoDB数据库名称
    """

    MONGO_DB_ENABLE: bool = Field(..., description="是否启用MongoDB")
    MONGO_DB_URL: MongoDsn = Field(..., description="MongoDB连接URL")
    MONGO_DB_NAME: str = Field(..., description="MongoDB数据库名称")


class DBSettings(BaseSettings):
    """数据库配置

    Attributes:
        ORM_DB: ORM配置
        REDIS_DB: Redis配置
        MONGO_DB: MongoDB配置
    """

    ORM_DB: ORMConfig = Field(..., description="ORM配置")
    REDIS_DB: RedisConfig = Field(..., description="Redis配置")
    MONGO_DB: MongoConfig = Field(..., description="MongoDB配置")


class FastAPISettings(BaseSettings):
    """FastAPI配置

    Attributes:
        TITLE: API标题
        DESCRIPTION: API描述
        HOST: 监听主机
        PORT: 监听端口
        VERSION: API版本
    """

    TITLE: str = Field(default="src-admin", description="API标题")
    DESCRIPTION: str = Field(default="", description="API描述")
    HOST: IPv4Address = Field(default="0.0.0.0", description="监听主机")
    PORT: int = Field(default=9000, description="监听端口")
    VERSION: str = Field(default="1.0.0", description="API版本")


class SystemSettings(BaseSettings):
    """系统配置

    Attributes:
        DEBUG: 是否开启调试模式
        LOG_CONSOLE_OUT: 是否输出日志到控制台
        LOG_PATH: 日志路径
        BASE_TEMPLATES_DIR: 模板目录
        BASE_PATH: 项目根目录
        TEMP_PATH: 临时文件目录
        STATIC_ENABLE: 是否启用静态文件
        STATIC_URL: 静态文件URL
        STATIC_PATH: 静态文件路径
        ASSETS_URL: 资源文件URL
        ASSETS_PATH: 资源文件路径
        CORS_ORIGIN_ENABLE: 是否启用CORS
        ALLOW_ORIGINS: 允许的源
        ALLOW_CREDENTIALS: 是否允许凭证
        ALLOW_METHODS: 允许的方法
        ALLOW_HEADERS: 允许的头部
        EVENTS: 全局事件
        OPERATION_LOG_RECORD: 是否记录操作日志
        DEFAULT_PASSWORD: 默认密码
        DEFAULT_AVATAR: 默认头像
        DEFAULT_AUTH_ERROR_MAX_NUMBER: 最大认证错误次数
        LOGIN_LOG_RECORD: 是否记录登录日志
        REQUEST_LOG_RECORD: 是否记录请求日志
        OPERATION_RECORD_METHOD: 记录的操作方法
        IGNORE_OPERATION_PATH: 忽略的操作路径
        MAX_ATTEMPTS: 最大尝试次数
        IP_RESOLVE_ENABLE: 是否启用IP解析
        IP_DB_PATH: IP数据库路径
        DESENSITIZE_FIELDS: 脱敏字段
    """

    DEBUG: bool = Field(default=False, description="是否开启调试模式")
    # 日志等级
    LOG_LEVEL: str = Field(default="ERROR", description="日志等级")
    LOG_CONSOLE_OUT: bool = Field(default=True, description="是否输出日志到控制台")
    LOG_PATH: str = Field(
        default=os.path.join(root_path, "logs"), description="日志路径"
    )
    BASE_TEMPLATES_DIR: str = Field(
        default=os.path.join(root_path, "templates", "build"), description="模板目录"
    )
    BASE_PATH: str = Field(default=root_path, description="项目根目录")
    TEMP_PATH: str = Field(
        default=os.path.join(root_path, "temp"), description="临时文件目录"
    )
    STATIC_ENABLE: bool = Field(default=True, description="是否启用静态文件")
    STATIC_URL: str = Field(default="/static", description="静态文件URL")
    STATIC_PATH: str = Field(
        default=os.path.join(root_path, "static"), description="静态文件路径"
    )
    ASSETS_URL: str = Field(default="/media", description="资源文件URL")
    ASSETS_PATH: str = Field(
        default=os.path.join(root_path, "assets"), description="资源文件路径"
    )
    CORS_ORIGIN_ENABLE: bool = Field(default=True, description="是否启用CORS")
    ALLOW_ORIGINS: List[str] = Field(default=["*"], description="允许的源")
    ALLOW_CREDENTIALS: bool = Field(default=True, description="是否允许凭证")
    ALLOW_METHODS: List[str] = Field(default=["*"], description="允许的方法")
    ALLOW_HEADERS: List[str] = Field(default=["*"], description="允许的头部")
    EVENTS: List[str] = Field(
        default=[f"{project_name}.core.event.db_event"], description="全局事件"
    )
    OPERATION_LOG_RECORD: bool = Field(default=True, description="是否记录操作日志")
    DEFAULT_PASSWORD: str = Field(default="0", description="默认密码")
    DEFAULT_AVATAR: str = Field(
        default="https://vv-reserve.oss-cn-hangzhou.aliyuncs.com/avatar/2023-01-27/1674820804e81e7631.png",
        description="默认头像",
    )
    DEFAULT_AUTH_ERROR_MAX_NUMBER: int = Field(
        default=5, description="最大认证错误次数"
    )
    LOGIN_LOG_RECORD: bool = Field(default=True, description="是否记录登录日志")
    REQUEST_LOG_RECORD: bool = Field(default=True, description="是否记录请求日志")
    OPERATION_RECORD_METHOD: List[str] = Field(
        default=["POST", "PUT", "DELETE"], description="记录的操作方法"
    )
    IGNORE_OPERATION_PATH: List[str] = Field(
        default=["/auth/login"], description="忽略的操作路径"
    )
    MAX_ATTEMPTS: int = Field(default=5, description="最大尝试次数")
    IP_RESOLVE_ENABLE: bool = Field(default=False, description="是否启用IP解析")
    IP_DB_PATH: str = Field(
        default=os.path.join(root_path, "resources", "ip2region.xdb"),
        description="IP数据库路径",
    )
    DESENSITIZE_FIELDS: List[str] = Field(
        default=["password", "old_password", "new_password", "phone"],
        description="脱敏字段",
    )

    @field_validator(
        "LOG_PATH", "BASE_TEMPLATES_DIR", "TEMP_PATH", "STATIC_PATH", "ASSETS_PATH"
    )
    def validate_paths(cls, v: str) -> str:
        """验证路径是否存在，不存在则创建"""
        os.makedirs(v, exist_ok=True)
        return v


class TaskSettings(BaseSettings):
    """定时任务配置

    Attributes:
        TASK_ENABLE: 是否启用任务引擎
        SCHEDULER_TASK_JOBS: 运行中任务集合
        TASK_PAG: 任务脚本目录
    """

    TASK_ENABLE: bool = Field(..., description="是否启用任务引擎")
    SCHEDULER_TASK_JOBS: str = Field(
        default=f"{project_name}.task_manage", description="运行中任务集合"
    )
    TASK_PAG: str = Field(
        default=f"{project_name}.task_manage", description="任务脚本目录"
    )


class Cache(BaseSettings):
    """缓存配置

    Attributes:
        REDIS_PREFIX: Redis键前缀
        TOKEN_KEY: token缓存键
        AUTH_ERROR_NUMBER_KEY: 认证错误次数键
        CAPTCHA_KEY: 验证码缓存键
        DICT_DATA_KEY: 数据字典键
        USER_KEY: 用户信息键
        ROLE_KEY: 角色权限键
    """

    REDIS_PREFIX: str = Field(default="", description="Redis键前缀")
    TOKEN_KEY: str = Field(default="token:", description="token缓存键")
    AUTH_ERROR_NUMBER_KEY: str = Field(
        default="auth_error_number:", description="认证错误次数键"
    )
    CAPTCHA_KEY: str = Field(default="captcha:", description="验证码缓存键")
    DICT_DATA_KEY: str = Field(default="sys_dict:", description="数据字典键")
    USER_KEY: str = Field(default="sys_user:", description="用户信息键")
    ROLE_KEY: str = Field(default="sys_role:", description="角色权限键")


class EmailConfig(BaseSettings):
    """邮件配置

    Attributes:
        MAIL_SERVER: 邮件服务器
        MAIL_PORT: 邮件服务器端口
        MAIL_USERNAME: 邮件用户名
        MAIL_PASSWORD: 邮件密码
        MAIL_STARTTLS: 是否使用STARTTLS
        MAIL_FROM: 发件人邮箱
        MAIL_SSL_TLS: 是否使用SSL/TLS
    """

    MAIL_SERVER: str = Field(..., description="邮件服务器")
    MAIL_PORT: int = Field(..., description="邮件服务器端口")
    MAIL_USERNAME: str = Field(..., description="邮件用户名")
    MAIL_PASSWORD: str = Field(..., description="邮件密码")
    MAIL_STARTTLS: bool = Field(default=True, description="是否使用STARTTLS")
    MAIL_FROM: str = Field(..., description="发件人邮箱")
    MAIL_SSL_TLS: bool = Field(default=False, description="是否使用SSL/TLS")


class WxSettings(BaseSettings):
    """微信配置

    Attributes:
        WX_APPID: 微信应用ID
        WX_SECRET: 微信应用密钥
        WX_REDIRECT_URI: 微信回调地址
    """

    WX_APPID: str = Field(default="wxf70e0235fc3f2701", description="微信应用ID")
    WX_SECRET: str = Field(
        default="c75226e25816cebec87611299157740b", description="微信应用密钥"
    )
    WX_REDIRECT_URI: str = Field(
        default="http://localhost:8000/auth/wx/callback", description="微信回调地址"
    )


class GlobalSettings(BaseSettings, case_sensitive=True):
    """全局配置

    Attributes:
        task: 定时任务配置
        demo: 演示环境配置
        auth: 认证配置
        db: 数据库配置
        system: 系统配置
        cache: 缓存配置
        email: 邮件配置
        wx: 微信配置
        FASTAPI_CONFIG: FastAPI配置
    """

    task: TaskSettings = Field(..., description="定时任务配置")
    demo: DemoSettings = Field(..., description="演示环境配置")
    auth: AuthSettings = Field(..., description="认证配置")
    db: DBSettings = Field(..., description="数据库配置")
    system: SystemSettings = Field(..., description="系统配置")
    cache: Cache = Field(default_factory=Cache, description="缓存配置")
    email: EmailConfig = Field(..., description="邮件配置")
    wx: WxSettings = Field(..., description="微信配置")
    FASTAPI_CONFIG: FastAPISettings = Field(..., description="FastAPI配置")

    model_config = SettingsConfigDict(
        extra="ignore",
        yaml_file=os.path.join(
            root_path, "config", f"{os.getenv('APP_ENV', default='dev')}.yaml"
        ),
        yaml_file_encoding="utf-8",
    )

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        """自定义配置源

        Returns:
            tuple[PydanticBaseSettingsSource, ...]: 配置源元组
        """
        return (
            init_settings,
            env_settings,
            YamlConfigSettingsSource(settings_cls),
        )


@lru_cache(maxsize=32)
def get_setting() -> GlobalSettings:
    """获取全局配置

    Returns:
        GlobalSettings: 全局配置对象
    """
    return GlobalSettings()


# 全局配置实例
settings = get_setting()

# if __name__ == "__main__":
#     print(GlobalSettings().model_dump())
